package com.tarena.lbs.content.serice.Impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.common.security.utils.LbsSecurityContenxt;
import com.tarena.lbs.content.serice.ArticleService;
import com.tarena.lbs.pojo.content.dto.ArticleContentDTO;
import com.tarena.lbs.pojo.content.es.ArticleDocs;
import com.tarena.lbs.pojo.content.param.ArticleContentParam;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.DateUtils;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.document.DocumentField;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult<ArticleContentDTO> queryList(ArticleQuery query) throws BusinessException{
        //查询结构 重点构造bool条件
        SearchRequest request=new SearchRequest("lbs_content");
        //封装请求参数 构造器
        SearchSourceBuilder builder=new SearchSourceBuilder();
        //使用source区分 当前查询是手机小程序查询=1 还是后台管理的查询=2
        Integer source = query.getSource();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        UserPrinciple loginToken = LbsSecurityContenxt.getLoginToken();
        if (loginToken==null){
            throw new BusinessException("101","登录用户无法获取");
        }
        Integer userId=loginToken.getId();
        if (source==1){
            //小程序查询 两个字条件
            //1 地理位置
            GeoPoint geoPoint = new GeoPoint(Double.valueOf(query.getLatitude()), Double.valueOf(query.getLongitude()));
            GeoDistanceQueryBuilder geoQuery = QueryBuilders.geoDistanceQuery("location");
            geoQuery.distance(50D, DistanceUnit.KILOMETERS).point(geoPoint);
            boolQueryBuilder.must(geoQuery);
            //2 label值相等 termQuery对比相等
            TermQueryBuilder termQuery = QueryBuilders.termQuery("articleLabel", query.getArticleLabel());
            boolQueryBuilder.must(termQuery);
        }else{
            //后台查询条件
            //1. 判断 搜索 是否 是当前登录用户的文章
            TermQueryBuilder userIdQuery = QueryBuilders.termQuery("userId", userId);
            boolQueryBuilder.must(userIdQuery);
            //2. 可能是空 非空的时候 才构造子条件
            String articleTitle = query.getArticleTitle();
            if (StringUtils.isNotEmpty(articleTitle)){
                //ariticleTitle 是text类型可以做全文匹配搜索 matchQuery
                MatchQueryBuilder articleTitleQuery = QueryBuilders.matchQuery("articleTitle", articleTitle);
                boolQueryBuilder.must(articleTitleQuery);
            }
            //文章状态 //上线下线
            List<Integer> articleStatus = query.getArticleStatus();
            if (CollectionUtils.isNotEmpty(articleStatus)){
                //循环 状态 每个状态 拼接子条件 都是或的关系
                articleStatus.stream().forEach(status->{
                    TermQueryBuilder statusQuery = QueryBuilders.termQuery("articleStatus", status);
                    //状态如果是多个值 0 1 查询的文章不可能同时满足
                    boolQueryBuilder.must(statusQuery);
                });
            }
            //更新时间段
            Date updateStartTime = query.getUpdateStartTime();
            Date updateEndTime = query.getUpdateEndTime();
            if (updateStartTime!=null && updateEndTime!=null){
                //updateTime字段 date类型 可以做范围range查询
                RangeQueryBuilder updateTimeQuery = QueryBuilders.rangeQuery("updateTime").gte(updateStartTime).lte(updateEndTime);
                boolQueryBuilder.must(updateTimeQuery);
            }

        }
        //分页
        int from=(query.getPageNo()-1)*query.getPageSize();
        builder.query(boolQueryBuilder);
        builder.from(from);
        builder.size(query.getPageSize());
        //排序 最好把最新的文章展示在列表上层 尤其是 后台管理
        builder.sort("createTime", SortOrder.DESC);
        request.source(builder);
        //查询结果 封装返回值 totalHits 分页数据
        PageResult<ArticleContentDTO> pageResult=new PageResult<>();
        try{
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            long total=response.getHits().getTotalHits().value;
            List<ArticleContentDTO> dtos=new ArrayList<>();
            if (total>0){
                //查询结果 分页结果的文档数组
                SearchHit[] hits = response.getHits().getHits();
                for (SearchHit hit : hits) {
                    //hit包含文档的json数据
                    String docJson=hit.getSourceAsString();
                    ArticleContentDTO articleContentDTO = JSON.parseObject(docJson, ArticleContentDTO.class);
                    hit.getIndex();
                    hit.getType();
                    hit.getScore();
                    String id = hit.getId();
                    articleContentDTO.setId(id);
                    dtos.add(articleContentDTO);
                }
            }
            pageResult.setTotal(total);
            pageResult.setObjects(dtos);
            pageResult.setPageNo(query.getPageNo());
            pageResult.setPageSize(query.getPageSize());
            return pageResult;
        }catch (Exception e){
            log.error("查询出现异常",e);
            throw new BusinessException("106","查询异常");
        }
    }

    @Override
    public void addArticle(ArticleContentParam param)  throws BusinessException{
        //1.新增创建文档 准备request
        IndexRequest request=new IndexRequest("lbs_content");
        ArticleDocs articleDoc=null;
        //2.articleDoc 根据参数封装完毕 写入请求中
        //2.1 先将param转化成和es索引对应数据的类型articleDoc
        articleDoc=new ArticleDocs();
        BeanUtils.copyProperties(param,articleDoc);
        //2.2 userId 创建人的id值
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        if (userPrinciple==null){
            throw new BusinessException("101","用户解析认证失败");
        }
        Integer userId=userPrinciple.getId();
        articleDoc.setUserId(userId);
        //2.3 地理位置 location "39.9041,116.4398"
        String location=param.getLatitude()+","+param.getLongitude();
        articleDoc.setLocation(location);
        //2.4 补充时间数据
        Date now=new Date();
        String nowstr = DateUtils.formatDate(now, "yyyy-MM-dd HH:mm:ss");
        articleDoc.setCreateTime(nowstr);
        articleDoc.setUpdateTime(nowstr);
        articleDoc.setArticleStatus(1);
        //2.5 将这个doc对象转化成请求可以携带的数据
        String jsonData=JSON.toJSONString(articleDoc);
        request.id(UUID.randomUUID().toString());
        request.source(jsonData, XContentType.JSON);
        //3.client客户端 新增到es中
        //4.新增文章的数据 写入es逻辑 不仅仅后台管理店铺账号可以新增,前台用户也可以新增.新增逻辑几乎没有什么区别
        try{
            client.index(request,RequestOptions.DEFAULT);
        }catch (Exception e){
            log.error("新增文章:{},失败",articleDoc,e);
        }
    }

    @Override
    public HashSet<String> getArticleLables(ArticleQuery articleQuery) {
        //1. 封装返回结果 等着从查询reponse获取数据
        HashSet<String> lables=new HashSet<>();
        //2. ES 标准查询流程
        //2.1 创建一个搜索查询request
        SearchRequest searchRequest=new SearchRequest("lbs_content");
        //2.2 查询的request定义各种查询的条件 范围50公里 去重 分页 取前20个标签
        SearchSourceBuilder builder = new SearchSourceBuilder();
        GeoDistanceQueryBuilder geoQuery = QueryBuilders.geoDistanceQuery("location");
        //中心点
        GeoPoint getPoint=new GeoPoint(Double.parseDouble(articleQuery.getLatitude()),Double.parseDouble(articleQuery.getLongitude()));
        //定义查询范围50公里
        geoQuery.distance(50D, DistanceUnit.KILOMETERS).point(getPoint);
        //builder添加query
        builder.query(geoQuery);
        //去重设置
        CollapseBuilder distinct=new CollapseBuilder("articleLabel");
        builder.collapse(distinct);
        //分页 取前20条
        builder.size(20);
        searchRequest.source(builder);
        //3. 查询获取响应
        try{
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            //拿到响应结果的集合
            SearchHit[] hits = searchResponse.getHits().getHits();//只取articleLabel
            log.info("查询结果命中:{}",searchResponse.getHits().getTotalHits().value);
            if (searchResponse.getHits().getTotalHits().value>0){
                for (SearchHit hit : hits){
                    //hit 就包含一个doc对象全属性 全数据
                    DocumentField articleLabel = hit.getFields().get("articleLabel");
                    String labelValue=articleLabel.getValue();
                    lables.add(labelValue);
                }
            }
        }catch (Exception e){
            log.error("查询标签集合出现异常",e);
        }
        //4. 解析响应 封装结果返回
        return lables;
    }

    @Override
    public ArticleContentDTO getArticleDetail(String id) {
        //1.利用id 注解获取文档 请求对象GetRequest 提供文档id
        GetRequest request=new GetRequest("lbs_content",id);
        //2.client调用get方法 获取文档
        try{
            GetResponse response = client.get(request, RequestOptions.DEFAULT);
            boolean exists = response.isExists();
            ArticleContentDTO dto=null;
            if (exists){
                //转化
                dto=JSON.parseObject(response.getSourceAsString(),ArticleContentDTO.class);
                dto.setId(id);
            }
            return dto;
        }catch (Exception e){
            log.error("查询文档id:{},失败",id,e);
        }
        return null;
    }
}
